package cn.lingyangwl.framework.tool.core;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * bean 和 map的互相转换
 * <p>
 * 在实际开发过程中，经常碰到需要进行对象与map之间互转的问题，其实对于对象、Map 之间进行互转有很多种方式，下面我们一起来梳理一下：
 * <p>
 * 1. 利用 JSON 工具包，将对象转成字符串，之后再转成 Map，这种需要转换2次，相对来说效率比较低
 * 2. 利用 Java 反射，获取 Bean 类的属性和值，再转换到 Map 对应的键值对中，相对来说这种方法效率高些，在实现上比较麻烦 (本人特别推荐)
 * 3. 利用 Java 的内省(Introspector) 实现，获取 Bean 类的属性和值，Map与对象互转，效率比较高
 * 4. 利用 apache 中的 BeanUtils工具包进行操作，底层实现类似方法三；
 * 5. 利用net.sf.cglib.beans.BeanMap类中的方法，这种方式效率也非常高
 * <p>
 * 以下采用反射方式进行转换, 经过测试 (效率最高)
 *
 * @author shenguangyang
 */
public class ObjectUtils {
    /**
     * 缓存对象字段, key = 类名
     */
    private static final Map<String, List<Field>> beanFieldCache = new ConcurrentHashMap<>();

    /**
     * 获取bean的所有字段
     *
     * @param object 对象
     * @return 类中所有字段信息, 不包含父类
     */
    public static List<Field> getFields(Object object) {
        return getFields(object.getClass());
    }

    /**
     * 获取bean的所有字段 (包括父类字段)
     *
     * @param targetClass 目标类
     * @return 类中所有字段信息, 不包含父类
     */
    public static List<Field> getFields(Class<?> targetClass) {
        String name = targetClass.getName();
        List<Field> fields = beanFieldCache.get(name);
        if (fields == null) {
            synchronized (name.intern()) {
                fields = beanFieldCache.get(name);
                if (fields == null) {
                    fields = new ArrayList<>();
                    Field[] fieldData = targetClass.getDeclaredFields();
                    for (Field field : fieldData) {
                        field.setAccessible(true);
                        fields.add(field);
                    }
                    beanFieldCache.put(name, fields);
                }
            }
        }
        return fields;
    }

    /**
     * 获取bean的所有字段 (包括父类字段)
     *
     * @param object 对象
     * @return 类中所有字段信息, 包含父类
     */
    public static List<Field> getFieldsWithSuper(Object object) {
        return getFieldsWithSuper(object.getClass());
    }

    /**
     * 获取bean的所有字段 (包括父类字段)
     *
     * @param targetClass 目标类
     * @return 获取类中所有字段信息, 包含父类
     */
    public static List<Field> getFieldsWithSuper(Class<?> targetClass) {
        String name = targetClass.getName();
        List<Field> fields = beanFieldCache.get(name);
        if (fields == null) {
            synchronized (name.intern()) {
                fields = beanFieldCache.get(name);
                if (fields == null) {
                    fields = new ArrayList<>();
                    while (targetClass != null) {
                        Field[] fieldData = targetClass.getDeclaredFields();
                        for (Field field : fieldData) {
                            field.setAccessible(true);
                            fields.add(field);
                        }
                        targetClass = targetClass.getSuperclass();
                    }
                    beanFieldCache.put(name, fields);
                }
            }
        }
        return fields;
    }


    /**
     * 对象转Map
     *
     * @param object bean对象
     * @return 目前只支持单层bean中所有字段提取到map中
     * @throws Exception 异常
     */
    public static Map<String, Object> beanToMap(Object object) throws Exception {
        Map<String, Object> map = new HashMap<>();
        List<Field> fields = getFieldsWithSuper(object);
        for (Field field : fields) {
            map.put(field.getName(), field.get(object));
        }
        return map;
    }

    /**
     * map转对象
     */
    public static <T> T mapToBean(Map<Object, Object> map, Class<T> beanClass) throws Exception {
        T object = beanClass.newInstance();
        List<Field> fields = getFieldsWithSuper(object);
        for (Field field : fields) {
            int mod = field.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }
            if (map.containsKey(field.getName())) {
                field.set(object, map.get(field.getName()));
            }
        }
        return object;
    }
}